<?php

error_reporting(E_ALL);
ini_set('display_errors', '1');

// 定义时区
ini_set('date.timezone','Asia/Shanghai');

class Weixinpay {	
    /**
     * 统一下单
     * @param  array $order 订单 必须包含支付所需要的参数 body(产品描述)、total_fee(订单金额)、out_trade_no(订单号)、product_id(产品id)、trade_type(类型：JSAPI，NATIVE，APP)
     */
    public function unifiedOrder($configs,$order,$ym){
    	if($configs['flag']=='xcx'){
    		$notify_url = $ym.'/index.php/Home/Xcxserver/Weixinpay/notify/key/'.$configs['key'].'.html';
    	}else{
    		$notify_url = $ym.'/index.php/Home/Mobile/Weixinpay/notify/key/'.$configs['key'].'.html';
    	}
        // 获取配置项
        if($configs['sub_mch_id']){
        	$config=array(
	            'appid'=>$configs['appid'],
	            'mch_id'=>$configs['mch_id'],
	            'sub_mch_id'=>$configs['sub_mch_id'],
	            'nonce_str'=>$this->createNonceStr(),
	            'spbill_create_ip'=>$this->get_client_ip(),
	            'notify_url'=>$notify_url
            );
            if($configs['sub_appid']){
            	$config['sub_appid'] = $configs['sub_appid'];
            }
        }else{
        	$config=array(
	            'appid'=>$configs['appid'],
	            'mch_id'=>$configs['mch_id'],
	            'nonce_str'=>$this->createNonceStr(),
	            'spbill_create_ip'=>$this->get_client_ip(),
	            'notify_url'=>$notify_url
            );
        }
        
        // 合并配置数据和订单数据
        $data=array_merge($order,$config);
        // 生成签名
        $sign=$this->makeSign($configs['key'],$data);
        $data['sign']=$sign;
        $xml=$this->toXml($data);
        $url = 'https://api.mch.weixin.qq.com/pay/unifiedorder';//接收xml数据的文件
        $header[] = "Content-type: text/xml";//定义content-type为xml,注意是数组
        $ch = curl_init ($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 兼容本地没有指定curl.cainfo路径的错误
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            // 显示报错信息；终止继续执行
            die(curl_error($ch));
        }
        curl_close($ch);
        $result=$this->toArray($response);
        // 显示错误信息
        if ($result['return_code']=='FAIL') {
            die($result['return_msg']);
        }
        $result['sign']=$sign;
        $result['nonce_str']=$config['nonce_str'];
        return $result;
    }


    /**
     * 验证
     * @return array 返回数组格式的notify数据
     */
    public function notify($key){
        // 获取xml
        $xml=file_get_contents('php://input', 'r'); 
        // 转成php数组
        $data=$this->toArray($xml);
        // 保存原sign
        $data_sign=$data['sign'];
        // sign不参与签名
        unset($data['sign']);
        $sign=$this->makeSign($key,$data);
        // 判断签名是否正确  判断支付状态
        if ($sign===$data_sign && $data['return_code']=='SUCCESS' && $data['result_code']=='SUCCESS') {
            $result=$data;
        }else{
            $result=false;
        }
        // 返回状态给微信服务器
        if ($result) {
            $str='<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
        }else{
            $str='<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>';
        }
        echo $str;
        return $result;
    }

    /**
     * 输出xml字符
     * @throws WxPayException
    **/
    public function toXml($data){		 
        $xml = "<xml>";
        foreach ($data as $key=>$val){
            if (is_numeric($val)){
                $xml.="<".$key.">".$val."</".$key.">";
            }else{
                $xml.="<".$key."><![CDATA[".$val."]]></".$key.">";
            }
        }
        $xml.="</xml>";
        return $xml; 
    }

    /**
     * 生成签名
     * @return 签名，本函数不覆盖sign成员变量，如要设置签名需要调用SetSign方法赋值
     */
    public function makeSign($key,$data){
        // 去空
        $data=array_filter($data);
        //签名步骤一：按字典序排序参数
        ksort($data);
        $string_a=http_build_query($data);
        $string_a=urldecode($string_a);
        //签名步骤二：在string后加入KEY
        $string_sign_temp=$string_a."&key=".$key;
        //签名步骤三：MD5加密
        $sign = md5($string_sign_temp);
        //签名步骤四：所有字符转为大写
        $result=strtoupper($sign);
        return $result;
    }

    /**
     * 将xml转为array
     * @param  string $xml xml字符串
     * @return array       转换得到的数组
     */
    public function toArray($xml){   
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $result= json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);        
        return $result;
    }

    /**
     * 获取jssdk需要用到的数据
     * @return array jssdk需要用到的数据
     */
    public function getParameters($config,$order,$ym){
        // 商品描述（需要根据自己的业务修改）
       	$order['body'] = $config['desc'];      	
        // 统一下单 获取prepay_id
        $unified_order=$this->unifiedOrder($config,$order,$ym);
      	// 获取当前时间戳
        $time=time();
        // 组合jssdk需要用到的数据
        
         if($config['app']=='y'){
			$app_res['appid'] = $unified_order['appid'];
			$app_res['noncestr'] = $unified_order['nonce_str'];
			$app_res['package'] = 'Sign=WXPay';
			$app_res['partnerid'] = $unified_order['mch_id'];
			$app_res['prepayid'] = $unified_order['prepay_id'];
			$app_res['timestamp'] = time();
			$sign=$this->makeSign($config['key'],$app_res);
			$app_res['sign'] = $sign;		
			return  $app_res;
		}else{
			if($config['xcx_appid']){
            	$appid = $config['xcx_appid'];
        	}else{
        		$appid = $config['appid'];
        	}
			
			$data=array(
				'appId'=>$appid, //appid
				'timeStamp'=>strval($time), //时间戳
				'nonceStr'=>$unified_order['nonce_str'],// 随机字符串
				'package'=>'prepay_id='.$unified_order['prepay_id'],// 预支付交易会话标识
				'signType'=>'MD5'//加密方式
			);
			// 生成签名
			$data['paySign']=$this->makeSign($config['key'],$data);
			$data['trade_type'] = $unified_order['trade_type'];
			$data['code_url'] = $unified_order['code_url'];
			return $data; 
        }
        
    }
	
	/**
     * 随机字符串
     */
	public function createNonceStr($length = 16){
        $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        $str = "";
        for ($i = 0; $i < $length; $i++) {
            $str .= substr($chars, mt_rand(0, strlen($chars) - 1), 1);
        }
        return $str;
    }
	
    /**
     * curl 请求http
     */
    public function curl_get_contents($url){
        $ch=curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);                //设置访问的url地址
        // curl_setopt($ch,CURLOPT_HEADER,1);               //是否显示头部信息
        curl_setopt($ch, CURLOPT_TIMEOUT, 5);               //设置超时
        curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']);   //用户访问代理 User-Agent
        curl_setopt($ch, CURLOPT_REFERER,$_SERVER['HTTP_HOST']);        //设置 referer
        curl_setopt($ch,CURLOPT_FOLLOWLOCATION,1);          //跟踪301
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);        //返回结果
        $r=curl_exec($ch);
        curl_close($ch);
        return $r;
    }

	/**
     * 退款
     */
	public function refund($config,$transaction_id,$out_refund_no,$total_fee,$refund_fee,$hotelid,$id,$refund_desc){
		//退款参数
		if($config['sub_mch_id']){
			$refundorder = array(
				'appid'			=> $config['appid'],
				'mch_id'		=> $config['mch_id'],
				'sub_mch_id'	=> $config['sub_mch_id'],
				'nonce_str'		=> $this->createNonceStr(),
				'transaction_id'=> $transaction_id,
				'out_refund_no'	=> $out_refund_no,
				'total_fee'		=> $total_fee * 100,
				'refund_fee'	=> $refund_fee * 100,
				'refund_desc'   => $refund_desc
			);
			if($config['sub_appid']){
				$refundorder['sub_appid'] = $config['sub_appid'];
			}
		}else{
			$refundorder = array(
				'appid'			=> $config['appid'],
				'mch_id'		=> $config['mch_id'],
				'nonce_str'		=> $this->createNonceStr(),
				'transaction_id'=> $transaction_id,
				'out_refund_no'	=> $out_refund_no,
				'total_fee'		=> $total_fee * 100,
				'refund_fee'	=> $refund_fee * 100,
				'refund_desc'   => $refund_desc
			);
		}
		
		$refundorder['sign'] = $this->makeSign($config['key'],$refundorder);
		//请求数据,进行退款
		$xml = $this->toXml($refundorder);
		$url = 'https://api.mch.weixin.qq.com/secapi/pay/refund';

		$res = $this->curl_post_ssl($url,$xml,$hotelid,$id);	

		return $res;
	}
	
	/**
     * POST请求
     */
	public function curl_post_ssl($url,$xml,$hotelid,$id,$second=30){	
	
		$ch = curl_init();
		//超时时间
		curl_setopt($ch,CURLOPT_TIMEOUT,$second);
		curl_setopt($ch,CURLOPT_RETURNTRANSFER, 1);
		
		curl_setopt($ch,CURLOPT_URL,$url);
		curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);
		curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false);
		
		//以下两种方式需选择一种
		/******* 此处必须为文件服务器根目录绝对路径 不可使用变量代替*********/
		curl_setopt($ch,CURLOPT_SSLCERT,dirname(__FILE__)."/../../php/wxpay/cert/".$hotelid."/apiclient_cert.pem");
		curl_setopt($ch,CURLOPT_SSLKEY,dirname(__FILE__)."/../../php/wxpay/cert/".$hotelid."/apiclient_key.pem");		

		curl_setopt($ch,CURLOPT_POST, 1);
		curl_setopt($ch,CURLOPT_POSTFIELDS,$xml);
		
		$xml = curl_exec($ch);	
		$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $data;
//		if($data['result_code']=='SUCCESS'){
//			$par['id'] = $id;
//		  	$datas['status'] = 4;
//		  	$result = M('order')->data($datas)->where($par)->save();
//			return '1';exit;
//		}else{
//			$par['id'] = $id;
//		  	$datas['status'] = 5;
//		  	$result = M('order')->data($datas)->where($par)->save();
//			return $data['err_code_des'];exit;
//		}
			
    }
	/**
     * 获取用户真实ip地址
    */
   	public function get_client_ip(){
   		$cip = "unknown";
   		if($_SERVER['REMOTE_ADDR']){
   			$cip = $_SERVER['REMOTE_ADDR'];
   		}else if(getenv("REMOTE_ADDR")){
   			$cip = getenv(['REMOTE_ADDR']);
   		}
   		return $cip;  		
   	}
   	
   	/**
	 * 微信打款
	 * 
	 */
	public function wechat_fight($config,$partner_trade_no,$openid,$hotelid,$id,$amount,$desc='打款'){
		$refundorder = array(
			'mch_appid'			=> $config['appid'],
			'mchid'		=> $config['mch_id'],
			'nonce_str'		=> $this->createNonceStr(),
			'partner_trade_no'	=> $partner_trade_no,
			'openid'		=> $openid,
			'check_name'	=> "NO_CHECK",
			'amount'   => $amount * 100,
			'desc' => "打款",
			'spbill_create_ip' => $this->get_client_ip()
		);
		$refundorder['sign'] = $this->makeSign($config['key'],$refundorder);
		//
		$xml = $this->toXml($refundorder);
		$url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers';

		$res = $this->curl_post_ssl($url,$xml,$hotelid,$id);
		//xml转数组
//		$xmljson= json_encode($res);
//		$res=json_decode($xmljson,true);
		
		return $res;
	}
   		
   	/**
	 * 扫码支付
	 * 
	 */
	public function micropay($config,$out_trade_no,$total_fee,$auth_code){
		//①、提交被扫支付
		if($config['sub_mch_id']){
			$data = array(
				'appid'=> $config['appid'],
				'mch_id'=> $config['mch_id'],
				'sub_mch_id'=> $config['sub_mch_id'],
				'nonce_str'=> $this->createNonceStr(),
				'body'=> $config['desc'],
				'out_trade_no'=> $out_trade_no,
				'total_fee'=> $total_fee * 100,
				'spbill_create_ip'=>$this->get_client_ip(),
				'auth_code'=>$auth_code
			);
			if($config['sub_appid']){
				$data['sub_appid'] = $config['sub_appid'];
			}
		}else{
			$data = array(
				'appid'=> $config['appid'],
				'mch_id'=> $config['mch_id'],
				'nonce_str'=> $this->createNonceStr(),
				'body'=> $config['desc'],
				'out_trade_no'=>$out_trade_no,
				'total_fee'=>$total_fee*100,
				'spbill_create_ip'=>$this->get_client_ip(),
				'auth_code'=>$auth_code
			);
		}
		
		$data['sign'] = $this->makeSign($config['key'],$data);

		$xml = $this->toXml($data);
		$url = 'https://api.mch.weixin.qq.com/pay/micropay';

        $header[] = "Content-type: text/xml";//定义content-type为xml,注意是数组
        $ch = curl_init ($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 兼容本地没有指定curl.cainfo路径的错误
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            // 显示报错信息；终止继续执行
            die(curl_error($ch));
        }
        curl_close($ch);
        $result = $this->toArray($response);
        //②、接口调用成功，明确返回调用失败
        if($result["return_code"] == "FAIL"){
			return $result["return_msg"];
		}
      
        if($result["return_code"] == "SUCCESS" && $result["result_code"] == "FAIL" && 
		   $result["err_code"] != "USERPAYING" && $result["err_code"] != "SYSTEMERROR")
		{
			return $result["err_code_des"];
		}
	
        //③、确认支付是否成功
		$queryTimes = 0;
		while($queryTimes < 30){
			$queryTimes++;
			$queryResult = $this->query($config,$out_trade_no);
			//如果需要等待1s后继续
			if($queryResult == 2){
				sleep(2);
				continue;
			} else if($queryResult == 0){//订单交易失败
				break;
			} else {//查询成功
				return $queryResult;
			}
		}
		
		return '订单交易失败';
	}
   	
   	//查询支付	
   	function query($config,$out_trade_no){
   		if($config['sub_mch_id']){
			$data = array(
				'appid'=> $config['appid'],
				'mch_id'=> $config['mch_id'],
				'sub_mch_id'=> $config['sub_mch_id'],
				'out_trade_no'=> $out_trade_no,
				'nonce_str'=> $this->createNonceStr()
			);
			if($config['sub_appid']){
				$data['sub_appid'] = $config['sub_appid'];
			}
		}else{
			$data = array(
				'appid'=> $config['appid'],
				'mch_id'=> $config['mch_id'],
				'out_trade_no'=>$out_trade_no,
				'nonce_str'=> $this->createNonceStr()
			);
		}
   		
		$data['sign'] = $this->makeSign($config['key'],$data);

		$xml = $this->toXml($data);
		$url = 'https://api.mch.weixin.qq.com/pay/orderquery';

        $header[] = "Content-type: text/xml";//定义content-type为xml,注意是数组
        $ch = curl_init ($url);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 兼容本地没有指定curl.cainfo路径的错误
        curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        $response = curl_exec($ch);
        if(curl_errno($ch)){
            // 显示报错信息；终止继续执行
            die(curl_error($ch));
        }
        curl_close($ch);
        $result = $this->toArray($response);
		if($result["return_code"] == "SUCCESS"  && $result["result_code"] == "SUCCESS")
		{
			//支付成功
			if($result["trade_state"] == "SUCCESS"){
			   	return $result;
			}
			//用户支付中
			else if($result["trade_state"] == "USERPAYING"){
				return 2;
			}
		}
		//如果返回错误码为“此交易订单号不存在”则直接认定失败
		if($result["err_code"] == "ORDERNOTEXIST"){
			return 0;
		} else{
			//如果是系统错误，则后续继续
			return 2;
		}
		return false;
   	}		
   		
   		
}
